﻿using System;
using RayDen.Library.Core;
using RayDen.Library.Entity.Interface;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Components {
    using Vector3 = Vector;

    public class PinholeCamera  : ICamera {

        
        protected Vector3 mRight;
        protected Vector3 mUp;
        protected Vector3 mLook;

        protected float mUlength = 0.0f;
        protected float mVlength = 0.0f;
        protected int w, h;

        public PinholeCamera(int width, int height) {
            w = width;
            h = height;
            TranslationVel = 1.0f;
            RotationVel = 1.0f;

            Aspect = width / (float)height;
            Fov = 45.0f;

            Position = Point.Zero;
            mRight = Vector3.UnitX;
            mUp = Vector3.UnitY;
            mLook = Vector3.UnitZ;
        }

        public void Yaw( float angle )
        {
            
            
            //Matrix4x4 rot = Matrix4x4.CreateRotationY( angle );

            //mRight = Vector3.TransformNormal( ref mRight, ref rot );
            //mLook = Vector3.TransformNormal( ref mLook, ref rot );
        }

        public void Pitch( float angle )
        {
            //Matrix4x4 rot = Matrix4x4.CreateRotationAxis( mRight, angle );

            //mUp = Vector3.TransformNormal( ref mUp, ref rot );
            //mLook = Vector3.TransformNormal( ref mLook, ref rot );
        }

        #region ICamera Members

        public Point Position { get; set; }
        public Vector3 Target { get; set; }
        public Vector3 Right { get { return mRight * mUlength; } set { mRight = value; } }
        public Vector3 Up { get { return mUp * mVlength; } set { mUp = value; } }
        public Vector3 Look { get { return mLook; } set { mLook = value; } }

        public float Aspect { get; set; }
        public float Fov { get; set; }

        public float TranslationVel { get; set; }
        public float RotationVel { get; set; }

        public virtual void Dolly( float dt )
        {
            Position += mLook * ( TranslationVel * dt );
        }

        public virtual void Pan( float dx, float dy )
        {
            dx *= TranslationVel;
            dy *= TranslationVel;

            Position += new Vector3( mRight.X * dx, 0.0f, mRight.Z * dx );
            Position += mUp * dy;
        }

        public virtual void Rotate( float dx, float dy )
        {
            Yaw( dx * RotationVel );
            Pitch( dy * RotationVel );
            BuildView();
        }

        public virtual void LookAt(Point pos, Point target, Vector3 up)
        {
            up.Normalize();

            Vector3 L = target - pos;
            L.Normalize();

            Vector3 R = Vector3.Cross( ref L, ref up );
            Vector3 U = Vector3.Cross( ref R, ref L );

            mUp = U;
            mRight = R;
            mLook = L;
            Position = pos;

            BuildView();
        }

        public virtual void BuildView()
        {
            float wlen = mLook.Length;
            mUlength = wlen * (float)Math.Tan( Fov * Math.PI / 180.0 );
            mVlength = mUlength / Aspect;

            mLook.Normalize();

            mUp = Vector3.Cross( ref mRight, ref mLook );
            mUp.Normalize();

            mRight = Vector3.Cross( ref mLook, ref mUp );
            mRight.Normalize();
        }

        public virtual void CenterOnBoundingBox( AABB box )
        {
            var eye = box.Center;
            var target = box.Center;
            var up = new Vector3(0.0f, 1.0f, 0.0f);

            eye.Z += 2.0f * ( box.Max - box.Min ).Length;
            LookAt( eye, target, up );
        }

        public virtual void CenterOnBoundingBox(AABB box, float scale)
        {
            var eye = box.Center;
            var target = box.Center;
            var up = new Vector3(0.0f, 1.0f, 0.0f);

            eye.Z += 2.0f * ( box.Max - box.Min ).Length * scale;
            LookAt( eye, target, up );
        }

        public virtual void CenterOnBoundingBox(AABB box, Vector3 up, int axis, float scale)
        {
            var eye = box.Center;
            var target = box.Center;

            eye[ axis ] += 2.0f * ( box.Max - box.Min ).Length * scale;
            LookAt( eye, target, up );
        }

        public void GetRay(double xp, double yp, out IRay cameraRay) {
            var u = (float)(2.0f * xp / w - 1.0f);
            var v = (float)(1.0f - 2.0f * yp / h);

            Vector rdir = mRight * u + mUp * v + this.Look;
            var rorig = (Position + rdir * 0.1f);
            rdir.Normalize();
            cameraRay = new RayInfo(rorig, rdir);
        }

        public float ZNear {
            get {
                throw new NotImplementedException();
            }
            set {
                throw new NotImplementedException();
            }
        }

        public float ZFar {
            get {
                throw new NotImplementedException();
            }
            set {
                throw new NotImplementedException();
            }
        }

        #endregion


        public void Sample(float x, float y, float u0, float u1, float u2, out IRay ray, out float pdf)
        {
            pdf = 1f;
            GetRay(x,y, out ray);
        }
    }
}
